iT邦幫忙

2024 iThome 鐵人賽

DAY 30
0
生成式 AI

2024 年用 LangGraph 從零開始實現 Agentic AI System系列 第 30

【Day 30】- 從 LangGraph 到使用者介面:整合 FastAPI 與 Streamlit 的全方位指南

  • 分享至 

  • xImage
  •  

摘要
文章首先概述了專案的核心目標,包括 理解自然語言查詢、進行即時網路搜尋、提供準確相關的答案、支援多輪對話、提供流暢的使用體驗。接著,文章深入分析了系統的架構,將系統劃分為 前端、後端和核心處理層 三個部分,並詳細介紹了每個部分的功能和使用的技術。核心處理層使用 LangGraph 來理解使用者問題,並決定如何回應。前端使用 Streamlit 來提供一個直覺的使用者介面。後端則使用 FastAPI 和 LangServe 來處理請求並與核心處理層通訊。
文章的後半部分深入探討了 LangGraph 的實作,包括 圖形結構、狀態管理、節點結構、FastAPI 整合和 Streamlit 前端實作 等方面。這些內容展示了 LangGraph 如何允許建立複雜的對話系統,如何透過狀態管理維護對話的連續性,以及如何使用 Streamlit 建立一個與 LangGraph 系統互動的使用者介面。

引言

在當今資訊爆炸的時代,快速獲取準確、相關的資訊變得越來越具有挑戰性。為了應對這一挑戰,我們開發了一個創新的 Web 搜尋助手,它結合了最先進的自然語言處理技術和直覺的使用者介面設計。這個專案旨在為使用者提供一個智慧、互動且友善的平台,使他們能夠輕鬆地進行網路搜尋並獲得高品質的回答。

我們的 Web 搜尋助手不僅僅是一個簡單的搜尋引擎,它是一個能夠理解上下文、進行多輪對話,並能夠利用外部工具來增強其能力的智慧系統。透過整合 LangGraph、FastAPI 和 Streamlit 等先進技術,我們創造了一個強大而靈活的解決方案,能夠滿足各種複雜的資訊需求。

首圖

專案概述

本專案的核心目標是創建一個智慧的 Web 搜尋助手,它能夠:

  1. 理解並處理自然語言查詢
  2. 進行即時網路搜尋以獲取最新資訊
  3. 提供準確、相關且容易理解的回答
  4. 支援多輪對話,允許使用者進行追問和澄清
  5. 透過直覺的使用者介面提供流暢的使用體驗

系統結構概述

你有沒有想過,當你問一個智慧助理問題時,背後究竟發生了什麼事?讓我們一起揭開這個神祕的面紗,如何?

Streamlit 截圖img

系統架構詳解

在深入探討各個元件之前,讓我們先對整個系統有一個宏觀的認識。我們的 Web 搜尋助手系統主要由三個核心部分組成:前端、後端和核心處理層。

前端使用 Streamlit 建構,提供了一個直覺、互動的使用者介面。後端由 FastAPI 和 LangServe 組成,負責處理請求並與核心處理層通訊。核心處理層則是由 LangGraph 驅動,負責自然語言理解、決策制定和工具呼叫。

系統概覽:三大核心元件

首先,我們的系統分為三個主要部分。你能猜到是哪三個嗎?沒錯,就是前端、後端和核心處理層。我們一個個來看:

  1. 前端 - 你眼前的這個介面:
    首先是前端,也就是你眼前看到的這個介面。我們使用了 Streamlit 來打造這個部分。它就像一個親切的櫃檯人員,隨時準備接收你的問題,並將結果呈現給你。Streamlit 的特點是簡潔易用,能夠快速呈現互動式的網頁介面。

  2. 後端 - 幕後的大腦:
    接著是後端,這裡有兩個重要角色:FastAPI 和 LangServe。FastAPI 負責接收前端傳來的請求,它的特點是處理速度快,而且支援非同步操作。LangServe 則是負責將請求轉換成 LangGraph 可以理解的格式。這兩個元件就像是效率超高的秘書,確保訊息能夠順利地在系統內部傳遞。

  3. 核心處理層 - LangGraph:
    最後是核心處理層,也就是我們的「大腦」- LangGraph。它主要由兩個部分組成:Chatbot 節點和 Tool 節點。Chatbot 節點負責理解使用者的問題,決定如何回應。如果需要額外資訊,它會向 Tool 節點求助。Tool 節點則可以連接外部資源,比如搜尋引擎或資料庫,來獲取所需的資訊。

系統結構 img

問題處理流程:從輸入到回答

現在,讓我們跟隨一個問題的旅程。假設你問:「最近的 AI 突破是什麼?」

這個問題首先會被 Streamlit 前端接收,然後打包成一個請求傳送到後端。FastAPI 接收到這個請求後,會將它交給 LangServe 進行必要的格式轉換。接著,問題就進入了 LangGraph。

在 LangGraph 中,Chatbot 節點首先會分析你的問題。它可能會發現這是一個需要最新資訊的問題,於是決定使用 Tool 節點來獲取最新的 AI 相關新聞。Tool 節點可能會使用預先設定好的搜尋 API 來查詢相關資訊。獲得資訊後,Chatbot 節點會整理這些資料,組織成一個清晰、易懂的回答。

完成處理後,回答會再次經過 LangServe 和 FastAPI,最後回到 Streamlit 前端,呈現在你的眼前。

LangGraph 實作

LangGraph 是一個強大的框架,允許我們創建複雜的對話系統。讓我們逐步分析其核心元件。

圖形結構實作

現在讓我們深入分析 graph.py 中的 LangGraph 實作:

# 創建圖形建構器
graph_builder = StateGraph(State)

# 新增節點
graph_builder.add_node(CHATBOT_NODE, chatbot)
graph_builder.add_node(TOOLS_NODE, tool_node)

# 新增邊
graph_builder.add_edge(START, CHATBOT_NODE)

# 新增條件邊
graph_builder.add_conditional_edges(
    CHATBOT_NODE,
    tools_condition,
)
graph_builder.add_edge(TOOLS_NODE, CHATBOT_NODE)

# 編譯圖形
graph = graph_builder.compile()

img

這個模組整合了所有元件,構建了完整的 LangGraph 結構:

  • 使用常數(如 TOOLS_NODE, CHATBOT_NODE)增強可讀性。
  • 清晰地定義圖的節點和邊。
  • 使用條件邊(add_conditional_edges)實作動態決策流程。

tools_condition 函式決定是否需要使用工具。如果需要,流程會轉到 TOOLS_NODE;否則,它會繼續在 CHATBOT_NODE。

這種結構允許系統根據需要靈活地在對話和工具使用之間切換。例如,當使用者詢問需要最新資訊的問題時,系統可以自動切換到搜尋工具,然後將結果返回給聊天機器人進行處理。

透過這種方式,我們創建了一個動態的、上下文感知的對話系統,能夠根據對話的需求靈活地使用外部工具和資源。

狀態管理

讓我們看看 state.py 檔案,它定義了系統的狀態管理:

from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph.message import add_messages

class State(TypedDict):
    messages: Annotated[list, add_messages]

這個簡單但強大的定義有以下幾個關鍵點:

  • 使用 TypedDict 確保型別安全,這在大型專案中尤為重要。
  • Annotated[list, add_messages] 是一個巧妙的設計,它允許我們輕鬆地向狀態中新增新訊息。
  • messages 欄位將儲存整個對話歷史,使系統能夠保持上下文感知。

這種狀態設計允許我們在整個圖中維護對話的連續性,是實現多輪對話的關鍵。

節點結構

接下來,讓我們看看系統中的兩個主要節點:工具節點和聊天機器人節點。

工具節點

import os
from app.core.config import settings
from langgraph.prebuilt import ToolNode
from langchain_community.tools.tavily_search import TavilySearchResults

# 創建 Tavily 搜尋工具實例
tool = TavilySearchResults(max_results=2)

# 使用 LangGraph 的 ToolNode 封裝工具
tool_node = ToolNode(tools=[tool])

這個模組專注於設置和配置搜尋工具:

  • 使用 TavilySearchResults 創建一個網路搜尋工具。
  • max_results=2 限制了每次搜尋返回的結果數量,有助於控制資訊量。
  • 使用 LangGraph 的 ToolNode 封裝工具,使其容易整合到圖中。

聊天機器人節點

llm = ChatOpenAI(
    temperature=0,
    max_tokens=512,
    timeout=None,
    max_retries=2,
)

llm_with_tools = llm.bind_tools([tool])

def chatbot(state: State):
    return {"messages": [llm_with_tools.invoke(state["messages"])]}

這個模組負責聊天機器人的核心邏輯:

  • 配置 ChatOpenAI 模型。
  • 將搜尋工具綁定到 LLM。
  • 定義 chatbot 函式,處理對話狀態。

FastAPI 整合

  • 如何使用 LangServe 創建 API 端點
  • API 路由和處理程序的實作

使用 LangServe 創建 API 端點

LangServe 是一個強大的工具,它簡化了將 LangChain 和 LangGraph 應用程式部署為 API 的過程。在 main.py 中,我們可以看到 LangServe 的使用:

from langserve import add_routes
from app.agents.search_agent.graph import graph

# ... (其他 FastAPI 設置程式碼)

add_routes(
    app,
    graph,
    path="/websearch",
)

這段程式碼的作用是:

  • 匯入 add_routes 函式和我們之前創建的 graph。
  • 使用 add_routes 函式將 graph 新增到 FastAPI 應用程式中。
  • 設置 API 端點路徑為 "/websearch"。

執行應用程式

在本機執行以下指令

./run_local.sh

Streamlit 前端實作

在我們深入了解 Streamlit 前端的具體實作之前,讓我們先回顧一下我們的系統架構。我們已經成功地使用 LangGraph 構建了一個強大的對話式搜尋引擎,並透過 FastAPI 和 LangServe 將其暴露為 API。現在,我們將使用 Streamlit 創建一個使用者友善的介面,使最終使用者能夠輕鬆地與我們的系統進行互動。

Streamlit 是一個極其強大且易用的 Python 函式庫,專為創建資料應用程式和機器學習模型的互動式 Web 介面而設計。在我們的案例中,它將成為連接使用者和我們複雜後端系統的橋樑。透過 Streamlit,我們將能夠快速構建一個直覺的聊天介面,即時顯示對話,並無縫地與我們的 LangServe API 進行通訊。

讓我們逐步解析這個 Streamlit 應用程式的實作:

1. 匯入和設置

首先,我們需要匯入必要的函式庫和設置基本配置:

import streamlit as st
from langchain.schema import HumanMessage, AIMessage
from langserve import RemoteRunnable
from typing import Optional

API_URL = "http://localhost:8000/websearch/"
app = RemoteRunnable(API_URL)

這部分程式碼匯入了必要的函式庫和類別,並設置了 LangServe 服務的 URL。RemoteRunnable 允許我們與遠端 LangServe API 進行互動。

2. 串流處理函式

接下來,我們定義了一個關鍵函式,用於處理來自 LangServe 的串流回應:

def stream_app_catch_tool_calls(inputs, thread) -> Optional[AIMessage]:
    tool_call_message = None
    assistant_response = ""
    for output in app.stream(inputs, thread, stream_mode="values"):
        for key, value in output.items():
            message = value["messages"][-1]
            if isinstance(message, AIMessage) and message.tool_calls:
                tool_call_message = message
            else:
                if isinstance(message, AIMessage):
                    assistant_response += message.content
                    st.write(message.content)
    return tool_call_message, assistant_response

這個函式負責串流處理來自 LangServe 的回應。它捕捉工具呼叫(如果有的話)並累積助理的回應。使用Streamlit 的 st.write() 即時顯示助理的回應。

3. Streamlit 介面設置

現在,讓我們開始構建我們的使用者介面:

st.title('網路查詢助手')
st.markdown("---")
st.markdown("提示: 您可以詢問各種問題,例如 '2024 鐵人賽關於 LangGraph 文章' 或 '高雄推薦餐廳有哪些?'")

if 'conversation_history' not in st.session_state:
    st.session_state.conversation_history = []

這部分設置了 Streamlit 應用的標題,新增了使用提示,並初始化了對話歷史。使用 st.session_state 來儲存對話歷史,確保在頁面重新整理時不會遺失。

4. 顯示對話歷史

接下來,我們將深入探討如何顯示對話歷史,處理使用者輸入,以及如何將所有這些元素整合成一個流暢的使用者體驗。

for message in st.session_state.conversation_history:
    with st.chat_message(message["role"]):
        st.write(message["content"])

這個迴圈遍歷對話歷史,使用 Streamlit 的 st.chat_message() 函式以聊天介面的形式顯示每條訊息。這種方法創建了一個視覺上吸引人的對話流,使使用者可以輕鬆回顧整個對話過程。透過使用 st.session_state,我們確保即使在頁面重新整理後,對話歷史也能保持不變,提供了持續的使用者體驗。

5. 使用者輸入處理

user_input = st.chat_input("請輸入您的查詢:")

if user_input:
    st.session_state.conversation_history.append({"role": "user", "content": user_input})
    
    with st.chat_message("user"):
        st.write(user_input)

這部分程式碼負責捕捉和處理使用者輸入。st.chat_input() 創建了一個聊天風格的輸入框,與整體介面風格保持一致。當使用者提交查詢時,我們立即將其新增到對話歷史中,並使用 st.chat_message("user") 在介面上顯示。這種即時回饋增強了應用的互動性,讓使用者感覺他們正在進行真實的對話。

6. 處理使用者查詢

with st.chat_message("assistant"):
    with st.spinner("正在處理您的查詢..."):
        try:
            thread = {"configurable": {"thread_id": "5"}}
            inputs = [HumanMessage(content=user_input)]
            
            tool_call_message, assistant_response = stream_app_catch_tool_calls(
                {"messages": inputs},
                thread
            )
            
            if assistant_response:
                st.session_state.conversation_history.append({"role": "assistant", "content": assistant_response})
            else:
                st.error("抱歉,我無法處理您的查詢。請稍後再試。")
                st.session_state.conversation_history.append({"role": "assistant", "content": "抱歉,我無法處理您的查詢。請稍後再試。"})
            
        except Exception as e:
            st.error(f"發生錯誤: {e}")
            st.session_state.conversation_history.append({"role": "assistant", "content": f"發生錯誤: {e}"})

這是應用程式的核心處理部分,它負責與後端 API 互動並處理回應:

  1. 使用 st.spinner() 顯示載入動畫,提供視覺回饋,讓使用者知道他們的查詢正在處理中。
  2. 呼叫 stream_app_catch_tool_calls() 函式處理使用者輸入。這個函式與我們的 LangServe API 互動,並處理串流回應。
  3. 如果成功獲得回應,將助理的回應新增到對話歷史中。
  4. 如果沒有獲得有效回應或發生錯誤,會顯示適當的錯誤訊息,並將錯誤資訊新增到對話歷史中。

7. 更新顯示

st.rerun()

使用 st.rerun() 強制 Streamlit 重新執行應用,這確保了最新的對話內容被即時顯示在介面上。這個簡單但重要的步驟保證了使用者總是看到最新的對話狀態。

8. 如何啟動Streamlit應用

要啟動Streamlit應用,請按照以下步驟操作:

  1. 確保你已經安裝了Streamlit。如果沒有,可以使用pip安裝:
pip install streamlit
  1. 將上述程式碼儲存成 streamlit_app.py,並執行以下指令:
streamlit run streamlit_app.py
  1. Streamlit 將自動在你的預設瀏覽器中開啟應用。通常地址是 http://localhost:8501

Streamlit 截圖img

總結

在本文中,我們詳細介紹了一個基於 LangGraph、FastAPI 和 Streamlit 的智慧 Web 搜尋助手系統。這個系統成功地整合了先進的自然語言處理技術、高效的後端服務和直覺的使用者介面,為使用者提供了一個強大而易用的資訊檢索工具。

透過 LangGraph 的靈活圖形結構,我們實現了複雜的對話管理和動態工具呼叫。FastAPI 和 LangServe 的結合為系統提供了高效、可擴展的後端支援。而 Streamlit 前端則確保了使用者能夠輕鬆地與系統進行互動。

即刻前往教學程式碼 Repo,親自搭建屬於自己的 LLM App 吧!別忘了給專案按個星星並持續關注更新,讓我們一起探索AI代理的新境界。

#參考資料:

  1. Fastapi
  2. LangServe

上一篇
【Day 29】- 網站開發遇上 AI:FastAPI、Streamlit 與 LangServe 的實戰指南
下一篇
【Day 31】- 忙碌上班族如何在鐵人賽中堅持30天寫作?從靈感到策略:9個關鍵步驟
系列文
2024 年用 LangGraph 從零開始實現 Agentic AI System31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言